package org.example.exam.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.example.exam.common.CacheConstants;
import org.example.exam.entity.PaperQuestion;
import org.example.exam.entity.Question;
import org.example.exam.entity.QuestionAnswer;
import org.example.exam.entity.QuestionChoice;
import org.example.exam.mapper.PaperQuestionMapper;
import org.example.exam.mapper.QuestionAnswerMapper;
import org.example.exam.mapper.QuestionChoiceMapper;
import org.example.exam.mapper.QuestionMapper;
import org.example.exam.service.QuestionService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.example.exam.utils.RedisUtils;
import org.example.exam.vo.QuestionQueryVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 题目Service实现类
 * 实现题目相关的业务逻辑
 */
@Slf4j
@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements QuestionService {

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private QuestionChoiceMapper questionChoiceMapper;
    @Autowired
    private QuestionAnswerMapper questionAnswerMapper;


    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private PaperQuestionMapper paperQuestionMapper;

    /**
     * 分页查询题目信息 实现方案2 进行分布查询
     * @param questionPage
     * @param questionQueryVo
     */
    @Override
    public void queryQuestionListByPage(Page<Question> questionPage, QuestionQueryVo questionQueryVo) {
        questionMapper.selectQuestionPage(questionPage,questionQueryVo);
    }

    /**
     * 分页查询题目信息 实现方案3 Java代码处理
     * @param questionPage
     * @param questionQueryVo
     */
    @Override
    public void queryQuestionListByStream(Page<Question> questionPage, QuestionQueryVo questionQueryVo) {
        // 1、题目单表的分页查询 + 动态条件查询（mybatis-plus）
        LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(questionQueryVo.getCategoryId() != null,Question::getCategoryId,questionQueryVo.getCategoryId());
        queryWrapper.eq(!ObjectUtils.isEmpty(questionQueryVo.getDifficulty()),Question::getDifficulty,questionQueryVo.getDifficulty());
        queryWrapper.eq(!ObjectUtils.isEmpty(questionQueryVo.getType()),Question::getType,questionQueryVo.getType());
        // 注意：Keyword是模糊查询
        queryWrapper.like(!ObjectUtils.isEmpty(questionQueryVo.getKeyword()),Question::getTitle,questionQueryVo.getKeyword());
        // 根据创建时间倒序排序
        queryWrapper.orderByDesc(Question::getCreateTime);
        this.page(questionPage,queryWrapper);

        if (ObjectUtils.isEmpty(questionPage.getRecords())) {
            // 如果没有满足条件的题目信息 直接返回就可以了
            log.debug("没有符合条件的题目信息，后续可以终止！直接返回结果即可！");
            return;
        }

        // 2、查询题目所对应的所有选项和答案
        // 我们不循环题目集合 questionPage.getRecords() 我们一次查询题目所有的答案和选项！再进行java代码处理！
        // todo:避免1+n的复杂度
        // 获取所有的题目ID
//        List<Long> questionIdList = questionPage.getRecords().stream()
//                .map(Question::getId)
//                .collect(Collectors.toList());
//        // 查询所有的选项
//        LambdaQueryWrapper<QuestionChoice>  questionChoiceWrapper = new LambdaQueryWrapper<>();
//        questionChoiceWrapper.in(QuestionChoice::getQuestionId,questionIdList);
//        List<QuestionChoice> questionChoices = questionChoiceMapper.selectList(questionChoiceWrapper);
//        // 查询所有的答案
//        LambdaQueryWrapper<QuestionAnswer>  questionAnswerWrapper = new LambdaQueryWrapper<>();
//        questionAnswerWrapper.in(QuestionAnswer::getQuestionId,questionIdList);
//        List<QuestionAnswer> questionAnswers = questionAnswerMapper.selectList(questionAnswerWrapper);
//
//        // 3、题目的选项和答案集合转成 map 格式（<key->题目id，value -> 题目对应的选项||题目对应的答案 >转为O(1)复杂度）
//        // 将数据内容映射为<question_id, questionAnswer对象>
//        Map<Long, QuestionAnswer> questionAnswerMap = questionAnswers.stream()
//                .collect(Collectors.toMap(
//                        QuestionAnswer::getQuestionId,
//                        answer -> answer
//                ));
//        // 将question_id相同的选项分组在一起
//        Map<Long, List<QuestionChoice>> questionChoiceMap = questionChoices.stream()
//                .collect(Collectors.groupingBy(QuestionChoice::getQuestionId));
//
//        // 4、循环题目列表，进行题目的选项和赋值工作
//        questionPage.getRecords().forEach(question -> {
//            // 给题目的答案赋值(题目一定有答案)
//            question.setAnswer(questionAnswerMap.get(question.getId()));
//            // 给题目的选项赋值(只有选择题才会有选项！选择题的Type = CHOICE)
//            if ("CHOICE".equals(question.getType())) {
//                // 只要是选项的操作 一定要考虑排序的问题
//                List<QuestionChoice> choiceList = questionChoiceMap.get(question.getId());
//                // 根据Sort字段从小打大正序排序
//                choiceList.sort(Comparator.comparing(QuestionChoice::getSort));
//                question.setChoices(choiceList);
//            }
//        });

        // todo:将上述代码提取出来封装成一个方法（快捷键：Ctrl+Alt+M） 以便其他函数复用
        fullQuestionChoiceAndAnswer(questionPage.getRecords());

    }

    // Ctrl+Alt+M 提取代码封装成一个函数
    private void fullQuestionChoiceAndAnswer(List<Question> questionList) {
        List<Long> questionIdList = questionList.stream()
                .map(Question::getId)
                .collect(Collectors.toList());
        // 查询所有的选项
        LambdaQueryWrapper<QuestionChoice>  questionChoiceWrapper = new LambdaQueryWrapper<>();
        questionChoiceWrapper.in(QuestionChoice::getQuestionId,questionIdList);
        List<QuestionChoice> questionChoices = questionChoiceMapper.selectList(questionChoiceWrapper);
        // 查询所有的答案
        LambdaQueryWrapper<QuestionAnswer>  questionAnswerWrapper = new LambdaQueryWrapper<>();
        questionAnswerWrapper.in(QuestionAnswer::getQuestionId,questionIdList);
        List<QuestionAnswer> questionAnswers = questionAnswerMapper.selectList(questionAnswerWrapper);

        // 3、题目的选项和答案集合转成 map 格式（<key->题目id，value -> 题目对应的选项||题目对应的答案 >转为O(1)复杂度）
        // 将数据内容映射为<question_id, questionAnswer对象>
        Map<Long, QuestionAnswer> questionAnswerMap = questionAnswers.stream()
                .collect(Collectors.toMap(
                        QuestionAnswer::getQuestionId,
                        answer -> answer
                ));
        // 将question_id相同的选项分组在一起
        Map<Long, List<QuestionChoice>> questionChoiceMap = questionChoices.stream()
                .collect(Collectors.groupingBy(QuestionChoice::getQuestionId));

        // 4、循环题目列表，进行题目的选项和赋值工作
        questionList.forEach(question -> {
            // 给题目的答案赋值(题目一定有答案)
            question.setAnswer(questionAnswerMap.get(question.getId()));
            // 给题目的选项赋值(只有选择题才会有选项！选择题的Type = CHOICE)
            if ("CHOICE".equals(question.getType())) {
                // 只要是选项的操作 一定要考虑排序的问题
                List<QuestionChoice> choiceList = questionChoiceMap.get(question.getId());
                // 根据Sort字段从小打大正序排序
                choiceList.sort(Comparator.comparing(QuestionChoice::getSort));
                question.setChoices(choiceList);
            }
        });
    }

    /**
     * 查询单个题目的详情
     *  多表查询：题目 + 答案 + 选项
     *  方案1：嵌套结果 连表查询 + result[可以使用 没有分页]
     *  方案2：嵌套查询 分布查询[可以使用 没有必要 1+n的复杂度]
     *  方案3：查询 + java代码赋值即可
     * @param id
     * @return
     */
    @Override
    public Question queryQuestionById(Long id) {
        // 1、查询题目对象
        Question question = this.getById(id);

        if (question == null) {
//            log.debug("查询id为{}的题目不存在！",id);
           throw new RuntimeException("查询id为%s的题目不存在！".formatted(id));
        }
        // 2、查询题目对应的答案
        LambdaQueryWrapper<QuestionAnswer>   questionAnswerWrapper = new LambdaQueryWrapper<>();
        questionAnswerWrapper.eq(QuestionAnswer::getQuestionId,id);
        QuestionAnswer questionAnswer = questionAnswerMapper.selectOne(questionAnswerWrapper);
        question.setAnswer(questionAnswer);
        // 3、查询题目对应的选项（只有选择题才有选项）
        if ("CHOICE".equals(question.getType())) {
            LambdaQueryWrapper<QuestionChoice>  questionChoiceWrapper = new LambdaQueryWrapper<>();
            questionChoiceWrapper.eq(QuestionChoice::getQuestionId,id);
            List<QuestionChoice> choiceList = questionChoiceMapper.selectList(questionChoiceWrapper);
            question.setChoices(choiceList);
        }

        // 5、进行Redis的数据缓存ZSet(创建一个子线程进行异步调用 不影响主线程的进行)
        new Thread(new Runnable() {
            @Override
            public void run() {
                incrementQuestionScore(question.getId());
            }
        }).start();

        return question;
    }
    /**
     * 该方法是进行题目加分的方法，在排行榜中进行加分
     * 被异步调用
     * @param questionId
     */
    private void incrementQuestionScore(Long questionId) {
        Double v = redisUtils.zIncrementScore(CacheConstants.POPULAR_QUESTIONS_KEY, questionId, 1);
        log.debug("完成id={}的题目的热榜分数累计，累计后的分数为“{}",questionId,v);
    }



    /**
     * 保存题目信息
     * @param question
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveQuestion(Question question) {
        // 1、先判断不能重复 同一个Type下（选择题、简答题、判断题）title不能重复
        LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Question::getType,question.getType());
        queryWrapper.eq(Question::getTitle,question.getTitle());
        long count = this.count(queryWrapper);
        if (count > 0) {
            throw new RuntimeException("保存题目%s失败！%s类型下已经有相同的题目！".formatted(
                    question.getTitle(),question.getType()
            ));
        }
        // 2、保存题目信息（先保存题目，保存了题目才有题目ID，才可以进行后续的答案和选项保存）
        // save()是mybatis-plus提供的方法 会自动主键回显 当我们保存到数据库之后，id字段就有值了
        this.save(question);
        // 3、判断是不是选择题：是，根据选项的正确给答案赋值 同时将选项插入到选项表
        QuestionAnswer answer = question.getAnswer(); // 获取答案对象
        answer.setQuestionId(question.getId());
        if ("CHOICE".equals(question.getType())) {
            List<QuestionChoice> choices = question.getChoices();
            // 创建一个用于拼接字符串的建造者
            StringBuilder builder = new StringBuilder();
            for(int i = 0; i < choices.size(); i++){
                QuestionChoice choice = choices.get(i);
                choice.setSort(i); // 给选项设置排序
                choice.setQuestionId(question.getId());
                // 保存选项【循环 + 数据库操作】因为选项的数据量不是特别大 所以在这里可以使用
                questionChoiceMapper.insert(choice);
                if (choice.getIsCorrect()) {
                    if (builder.length() > 0) {
                        builder.append(',');
                    }
                    builder.append((char)('A'+i));
                };
            }
            // 通过选项给答案赋值
            answer.setAnswer(builder.toString());
        }

        // 4、完成答案数据的插入
        questionAnswerMapper.insert(answer);

    }

    /**
     * 更新题目信息
     * @param question
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateQuestion(Question question) {
        // 1、进行判断 不同的题目id的title不能相同
        LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Question::getTitle, question.getTitle());
        queryWrapper.ne(Question::getId, question.getId());
        long count = this.count(queryWrapper);
        if (count > 0) {
            throw new RuntimeException("更新失败！题目的新title：%s 已经被其他题目使用".formatted(question.getTitle()));
        }

        // 2、进行题目信息的更新
        this.updateById(question);

        // 3、获取答案对象
        // 答案对象有 id
        QuestionAnswer answer = question.getAnswer();

        // 4、判断是否为选择题【1、删除原有选项 2、添加新的选项 3、拼接正确的选项 4、给答案对象进行答案赋值】
        if ("CHOICE".equals(question.getType())) {
            List<QuestionChoice> choices = question.getChoices();
            // 删除原有选项
            LambdaQueryWrapper<QuestionChoice>  questionChoiceWrapper = new LambdaQueryWrapper<>();
            questionChoiceWrapper.eq(QuestionChoice::getQuestionId,question.getId());
            questionChoiceMapper.delete(questionChoiceWrapper);
            // 添加新的答案
            StringBuilder builder = new StringBuilder();
            for(int i = 0; i < choices.size(); i++){
                QuestionChoice choice = choices.get(i);
                choice.setId(null);
                choice.setUpdateTime(null);
                choice.setCreateTime(null);
                choice.setSort(i);
                choice.setQuestionId(question.getId());
                questionChoiceMapper.insert(choice);
                // 拼接新的答案
                if (choice.getIsCorrect()) {
                    if (builder.length() > 0) {
                        builder.append(',');
                    }
                    builder.append((char)('A'+i));
                }
            }
            answer.setAnswer(builder.toString());
        }

        // 5、进行答案对的更新
        questionAnswerMapper.updateById(answer);
    }

    /**
     * 删除题目信息
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeQuestion(Long id) {
        // 1、检查是否有关联的试卷题目，有的话 删除失效
        LambdaQueryWrapper<PaperQuestion>  queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PaperQuestion::getQuestionId,id);
        Long count = paperQuestionMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new RuntimeException("删除id=%s的题目失败！该题目已经被试卷使用！使用次数：%s".formatted(id, count));
        }
        // 2、删除题目本身
        this.removeById(id);
        // 3、删除关联的子数据：选项和答案
        LambdaQueryWrapper<QuestionChoice>  questionChoiceWrapper = new LambdaQueryWrapper<>();
        questionChoiceWrapper.eq(QuestionChoice::getQuestionId,id);
        questionChoiceMapper.delete(questionChoiceWrapper);

        LambdaQueryWrapper<QuestionAnswer>  questionAnswerWrapper = new LambdaQueryWrapper<>();
        questionAnswerWrapper.eq(QuestionAnswer::getQuestionId,id);
        questionAnswerMapper.delete(questionAnswerWrapper);
        // 4、添加事务注解

    }

    /**
     * 查询而我们题目
     * @param size
     * @return
     */
    @Override
    public List<Question> queryPopularQuestionList(Integer size) {
        // 定义一个集合用来存储热门题目
        List<Question> papularQuestionList = new ArrayList<>();


        // 1、查询Redis中缓存的题目id!（按照顺序访问倒序查询）
        // 查出的结果是按照题目的访问次数倒序查询的
        Set<Object> popularQuestionIdList = redisUtils.zReverseRange(CacheConstants.POPULAR_QUESTIONS_KEY, 0, size - 1);
        // 2、根据查询的id查询对应的热门题目集合
        if (!ObjectUtils.isEmpty(popularQuestionIdList)) {
            // 处理题目热门题目 处理后的结果也是按照题目的查询次数来倒序查询的
            List<Long> list = popularQuestionIdList.stream()
                    .map(id -> Long.valueOf(id.toString()))
                    .collect(Collectors.toList());
            // 2.1、批量获取题目信息
            // 但是从数据库里面查询到的数据有可能不是按照访问此时倒序查询的
//            List<Question> questionList = this.listByIds(list);
            for (Long id : list) {
                // 一个一个查就不会打乱顺序了
                Question question = this.getById(id);
                // id存在 但是题目已经被删除！redis的数据和数据库中的数据不同步问题！
                if (question != null) {
                    papularQuestionList.add(question);
                }
            }
        }

        // 3、查询热门题目集合数量是否满足size条
        // 最少最少 diff = 0 不可能为负数 因为查询的条数最多最多也就是size条 不可能比size更多
        int diff = size - papularQuestionList.size();
        // 4、不满足size条 则补充（题目表查询 --> 查询最近的题目）
        if (diff > 0) {
            // 从数据库查询最新的题目 查询 diff个
            LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.orderByDesc(Question::getCreateTime);

            // 已有的id进行过滤排除
            List<Long> existQuestionId = papularQuestionList.stream()
                    .map(Question::getId)
                    .collect(Collectors.toList());


            // 已经在集合中的题目不能再次插入到集合中
            queryWrapper.notIn(
                    ObjectUtils.isEmpty(existQuestionId),
                    Question::getId, existQuestionId
            );

            // 切割指定的diff条
            // 记得一定要在 limit 后面加空格
            queryWrapper.last("limit " + diff); // 在我们的SQL语句最后添加一段SQL！！！


            List<Question> newQuestionList = this.list(queryWrapper);
            // 添加到热门题目集合当中
            papularQuestionList.addAll(newQuestionList);
        }

        // 5、给题目进行选项和答案赋值即可
        // 使用复用的方法
        fullQuestionChoiceAndAnswer(papularQuestionList);

        return papularQuestionList;
    }


}